Overview
CountDownLatch
本身实现非常简单, 观察内部几乎所有方法都是直接调用AQS. 先看下它的典型用法:
1 | CountDownLatch latch = new CountDownLatch(2); |
对比CountDownLatch
和Worker
, 前者的await
有些类似后者的lock
, 而countDown
类似unlock
. 然而Worker
是一种排他的同步工具, 而CountDownLatch
则不是. 在上面的栗子中, thread1
, thread2
都会处于等待状态, 直到thread3
, thread4
执行完成.
根据上面的代码可以确定三个入口: 构造方法, await
, countDown
. 观察CountDownLatch
的实现, 其中包含一个继承AQS的类Sync
. 构造方法中, 只是设置state
值; 传入的参数count
即为AQS的state
. countDown
方法中将state
减一, 为0时await
的线程即将结束等待.
await
如图:
如果对比Worker#lock
的流程图, 可以发现两者的结构很类似. 实际上, 很多方法都是可以一一对应, 在后面会列一个表格出来. 图中tryAcquireShared
由子类实现, 代码如下:
1 | protected int tryAcquireShared(int acquires) { |
- 入口处如果
getState != 0
(实际上就是大于0), 那么就直接进入等待队列. 否则getState == 0
, 实际上就不需要等待了, 直接return
- 循环中,
getState == 0
, 即可以退出循环.
综上, tryAcquireShared
的结果大于0, 即表示acquire成功.
doReleaseShared
方法, 在下文countDown
处说明.
await(long timeout, TimeUnit unit)
这个重载方法和await
区别不大. 它内部调用的是AQS#doAcquireSharedNanos
. 观察其中代码可以发现几乎没有任何区别, 只不过多了时间控制. 其中值得注意的是, 如果时间小于spinForTimeooutThreadhold
, 则通过自旋(即循环), 否则通过LockSupport.partNanos来实现, 这样实现是处于性能考虑, 注释中亦有说明.
countDown
CountDownLatch#countDown
即调用Sync#releaseShared(1)
. 其中代码很短:1
2
3
4
5if (tryReleaseShared(1)) {
doReleaseShared();
return true;
}
return false;
而tryReleaseShared
的内容可以概括为: 尝试将state
减一, 如果成功则返回true
.
如果tryReleaseShared
成功, 则调用doReleaseShared
. 此方法在上文也有提到, 代码如下:
1 | private void doReleaseShared() { |
- 仅当
h == head
时才能跳出循环. 也就是说如果执行期间head
被修改了, 需要重新执行操作. - 如果
head.waitStatus
为SIGNAL
, 则将其设置CAS为0, 并unpark
其后置节点. - 如果
head.waitStatus
为0, 则将其设置为PROPAGATE
, 进入下次循环. 这里比较值得说明一下, 在我们刚刚设想的理想状态下,head.waitStatus
是应该为SIGNAL
的. 为0说明此时还没有其他节点进来, TODO
整体过程Review
通过几个重要的变量, 来说明下整个过程的数据变化:
- 当
Thread3
未开始执行时,state == 2
,Thread3
第一次countDown
,state
只是变成1, 所以tryReleaseShared
返回false
. - 当
Thread4
再次countDown
时, 才返回true
. 此时, 调用doReleaseShared
.Thread1
被unpark
, 跳出循环. 回到doAcquireSharedInterruptibly
, 随后执行setHeadAndPropagate
,其中再次执行了doReleaseShared, 从而Thread2
也被唤醒.